package com.jl;

import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import java.util.ArrayList;
import java.util.List;

/**
 * mongo查询条件构造器
 */
public class JLQuery {

    public final static String EQ = "eq", LT = "lt", GT = "gt", LE = "le", GE = "ge", LIKE = "like", NE = "ne", IN = "in", NOT_IN = "notIn";

    /**
     * 获取query
     *
     * @param param mongo实体对象
     * @return
     */
    public <T> Operate<T> getQuery(T param) {
        return new Operate(param, EQ);
    }

    /**
     * 获取query
     *
     * @param param  入参实体对象
     * @param claszz mongo实体class
     * @return
     */
    public <T> Operate<T> getQuery(Object param, Class<T> claszz) {
        return new Operate(param, claszz, EQ);
    }

    /**
     * 获取query
     *
     * @param param mongo实体对象
     * @return
     */
    public <T> Operate<T> eqQuery(T param) {
        return new Operate(param, EQ);
    }

    /**
     * 获取query
     *
     * @param param  入参实体对象
     * @param claszz mongo实体class
     * @return
     */
    public <T> Operate<T> eqQuery(Object param, Class<T> claszz) {
        return new Operate(param, claszz, EQ);
    }

    /**
     * 获取query
     *
     * @param param mongo实体对象
     * @return
     */
    public <T> Operate<T> ltQuery(T param) {
        return new Operate(param, LT);
    }

    /**
     * 获取query
     *
     * @param param  入参实体对象
     * @param claszz mongo实体class
     * @return
     */
    public <T> Operate<T> ltQuery(Object param, Class<T> claszz) {
        return new Operate(param, claszz, LT);
    }

    /**
     * 获取query
     *
     * @param param mongo实体对象
     * @return
     */
    public <T> Operate<T> gtQuery(T param) {
        return new Operate(param, GT);
    }

    /**
     * 获取query
     *
     * @param param  入参实体对象
     * @param claszz mongo实体class
     * @return
     */
    public <T> Operate<T> gtQuery(Object param, Class<T> claszz) {
        return new Operate(param, claszz, GT);
    }

    /**
     * 获取query
     *
     * @param param mongo实体对象
     * @return
     */
    public <T> Operate<T> leQuery(T param) {
        return new Operate(param, LE);
    }

    /**
     * 获取query
     *
     * @param param  入参实体对象
     * @param claszz mongo实体class
     * @return
     */
    public <T> Operate<T> leQuery(Object param, Class<T> claszz) {
        return new Operate(param, claszz, LE);
    }

    /**
     * 获取query
     *
     * @param param mongo实体对象
     * @return
     */
    public <T> Operate<T> geQuery(T param) {
        return new Operate(param, GE);
    }

    /**
     * 获取query
     *
     * @param param  入参实体对象
     * @param claszz mongo实体class
     * @return
     */
    public <T> Operate<T> geQuery(Object param, Class<T> claszz) {
        return new Operate(param, claszz, GE);
    }

    /**
     * 获取query
     *
     * @param param mongo实体对象
     * @return
     */
    public <T> Operate<T> likeQuery(T param) {
        return new Operate(param, LIKE);
    }

    /**
     * 获取query
     *
     * @param param  入参实体对象
     * @param claszz mongo实体class
     * @return
     */
    public <T> Operate<T> likeQuery(Object param, Class<T> claszz) {
        return new Operate(param, claszz, LIKE);
    }

    /**
     * 获取query
     *
     * @param param mongo实体对象
     * @return
     */
    public <T> Operate<T> neQuery(T param) {
        return new Operate(param, NE);
    }

    /**
     * 获取query
     *
     * @param param  入参实体对象
     * @param claszz mongo实体class
     * @return
     */
    public <T> Operate<T> neQuery(Object param, Class<T> claszz) {
        return new Operate(param, claszz, NE);
    }

    /**
     * 获取query
     *
     * @param param mongo实体对象
     * @return
     */
    public <T> Operate<T> inQuery(T param) {
        return new Operate(param, NE);
    }

    /**
     * 获取query
     *
     * @param param  入参实体对象
     * @param claszz mongo实体class
     * @return
     */
    public <T> Operate<T> inQuery(Object param, Class<T> claszz) {
        return new Operate(param, claszz, NE);
    }

    /**
     * 获取query
     *
     * @param param mongo实体对象
     * @return
     */
    public <T> Operate<T> notInQuery(T param) {
        return new Operate(param, NOT_IN);
    }

    /**
     * 获取query
     *
     * @param param  入参实体对象
     * @param claszz mongo实体class
     * @return
     */
    public <T> Operate<T> notInQuery(Object param, Class<T> claszz) {
        return new Operate(param, claszz, NOT_IN);
    }

    public static class Operate<T> {
        private Object param;
        private Class<T> claszz;
        private List<String> notDefaultProperty = new ArrayList<>();
        private List<JLTuple.Tuple3<String, String, Object>> arithmetics = new ArrayList<>();
        List<JLTuple.Tuple2<String, String>> orderBy = new ArrayList<>();
        private String defaults;

        public Operate(Object param, String defaults) {
            this.param = param;
            this.defaults = defaults;
        }

        public Operate(Object param, Class<T> claszz, String defaults) {
            this.param = param;
            this.claszz = claszz;
            this.defaults = defaults;
        }

        /**
         * 默认
         */
        private Criteria defaults(Object param) {
            Criteria criteria = new Criteria();
            List<JLTuple.Tuple3<String, Object, Class<?>>> tuple3s = JLReflect.PropertyReflect.getProperty(param);
            List<JLTuple.Tuple3<String, String, Object>> defaultArithmetics = new ArrayList<>();
            for (JLTuple.Tuple3<String, Object, Class<?>> tuple3 : tuple3s) {
                if (notDefaultProperty.contains(tuple3.getV1())) {
                    continue;
                }
                defaultArithmetics.add(new JLTuple.Tuple3<>(tuple3.getV1(), defaults, tuple3.getV2()));
            }
            arithmetic(criteria, defaultArithmetics);
            return criteria;
        }

        /**
         * 默认
         */
        private Criteria defaults(Object param, Class<T> claszz) {
            Criteria criteria = new Criteria();
            List<JLTuple.Tuple3<String, Object, Class<?>>> tuple3s = JLReflect.PropertyReflect.getProperty(param);
            List<JLTuple.Tuple3<String, Object, Class<?>>> tuple3sr = JLReflect.PropertyReflect.getProperty(claszz);
            List<JLTuple.Tuple3<String, String, Object>> defaultArithmetics = new ArrayList<>();
            for (JLTuple.Tuple3<String, Object, Class<?>> tuple3 : tuple3s) {
                String property = tuple3.getV1();
                Object value = tuple3.getV2();
                if (notDefaultProperty.contains(property)) {
                    continue;
                }
                for (JLTuple.Tuple3<String, Object, Class<?>> tuple3r : tuple3sr) {
                    if (property.equals(tuple3r.getV1())) {
                        defaultArithmetics.add(new JLTuple.Tuple3<>(property, defaults, value));
                        break;
                    }
                }
            }
            arithmetic(criteria, defaultArithmetics);
            return criteria;
        }

        /**
         * =
         */
        public Operate<T> eq(JLLambda.JLFunction<T, ?> jlFunction, Object value) {
            set(jlFunction, value, EQ);
            return this;
        }

        /**
         * =
         */
        public Operate<T> eq(JLLambda.JLFunction<T, ?> jlFunction) {
            JLTuple.Tuple3<String, Object, Class<?>> tuple3 = JLReflect.PropertyReflect.getProperty(param, JLLambda.getProperty(jlFunction));
            eq(jlFunction, tuple3.getV2());
            return this;
        }

        /**
         * <
         */
        public Operate<T> lt(JLLambda.JLFunction<T, ?> jlFunction, Object value) {
            set(jlFunction, value, LT);
            return this;
        }

        /**
         * <
         */
        public Operate<T> lt(JLLambda.JLFunction<T, ?> jlFunction) {
            JLTuple.Tuple3<String, Object, Class<?>> tuple3 = JLReflect.PropertyReflect.getProperty(param, JLLambda.getProperty(jlFunction));
            lt(jlFunction, tuple3.getV2());
            return this;
        }

        /**
         * >
         */
        public Operate<T> gt(JLLambda.JLFunction<T, ?> jlFunction, Object value) {
            set(jlFunction, value, GT);
            return this;
        }

        /**
         * >
         */
        public Operate<T> gt(JLLambda.JLFunction<T, ?> jlFunction) {
            JLTuple.Tuple3<String, Object, Class<?>> tuple3 = JLReflect.PropertyReflect.getProperty(param, JLLambda.getProperty(jlFunction));
            gt(jlFunction, tuple3.getV2());
            return this;
        }

        /**
         * <=
         */
        public Operate<T> le(JLLambda.JLFunction<T, ?> jlFunction, Object value) {
            set(jlFunction, value, LE);
            return this;
        }

        /**
         * <=
         */
        public Operate<T> le(JLLambda.JLFunction<T, ?> jlFunction) {
            JLTuple.Tuple3<String, Object, Class<?>> tuple3 = JLReflect.PropertyReflect.getProperty(param, JLLambda.getProperty(jlFunction));
            le(jlFunction, tuple3.getV2());
            return this;
        }

        /**
         * >=
         */
        public Operate<T> ge(JLLambda.JLFunction<T, ?> jlFunction, Object value) {
            set(jlFunction, value, GE);
            return this;
        }

        /**
         * >=
         */
        public Operate<T> ge(JLLambda.JLFunction<T, ?> jlFunction) {
            JLTuple.Tuple3<String, Object, Class<?>> tuple3 = JLReflect.PropertyReflect.getProperty(param, JLLambda.getProperty(jlFunction));
            ge(jlFunction, tuple3.getV2());
            return this;
        }

        /**
         * like
         */
        public Operate<T> like(JLLambda.JLFunction<T, ?> jlFunction, Object value) {
            set(jlFunction, value, LIKE);
            return this;
        }

        /**
         * like
         */
        public Operate<T> like(JLLambda.JLFunction<T, ?> jlFunction) {
            JLTuple.Tuple3<String, Object, Class<?>> tuple3 = JLReflect.PropertyReflect.getProperty(param, JLLambda.getProperty(jlFunction));
            like(jlFunction, tuple3.getV2());
            return this;
        }

        /**
         * ne
         */
        public Operate<T> ne(JLLambda.JLFunction<T, ?> jlFunction, Object value) {
            set(jlFunction, value, NE);
            return this;
        }

        /**
         * ne
         */
        public Operate<T> ne(JLLambda.JLFunction<T, ?> jlFunction) {
            JLTuple.Tuple3<String, Object, Class<?>> tuple3 = JLReflect.PropertyReflect.getProperty(param, JLLambda.getProperty(jlFunction));
            ne(jlFunction, tuple3.getV2());
            return this;
        }

        /**
         * in
         */
        public Operate<T> in(JLLambda.JLFunction<T, ?> jlFunction, List<?> value) {
            set(jlFunction, value, IN);
            return this;
        }

        /**
         * in
         */
        public Operate<T> in(JLLambda.JLFunction<T, ?> jlFunction) {
            JLTuple.Tuple3<String, Object, Class<?>> tuple3 = JLReflect.PropertyReflect.getProperty(param, JLLambda.getProperty(jlFunction));
            if (tuple3.getV2() != null) {
                in(jlFunction, (List) tuple3.getV2());
            }
            return this;
        }

        /**
         * notIn
         */
        public Operate<T> notIn(JLLambda.JLFunction<T, ?> jlFunction, List<?> value) {
            set(jlFunction, value, NOT_IN);
            return this;
        }

        /**
         * notIn
         */
        public Operate<T> notIn(JLLambda.JLFunction<T, ?> jlFunction) {
            JLTuple.Tuple3<String, Object, Class<?>> tuple3 = JLReflect.PropertyReflect.getProperty(param, JLLambda.getProperty(jlFunction));
            if (tuple3.getV2() != null) {
                notIn(jlFunction, (List) tuple3.getV2());
            }
            return this;
        }

        /**
         * 正序
         */
        public Operate<T> asc(JLLambda.JLFunction<T, ?> jlFunction) {
            String property = JLLambda.getProperty(jlFunction);
            orderBy.add(new JLTuple.Tuple2<>(property, "asc"));
            return this;
        }

        /**
         * 倒序
         */
        public Operate<T> desc(JLLambda.JLFunction<T, ?> jlFunction) {
            String property = JLLambda.getProperty(jlFunction);
            orderBy.add(new JLTuple.Tuple2<>(property, "desc"));
            return this;
        }

        /**
         * 条件器
         */
        public Criteria criteria() {
            //默认
            Criteria criteria = claszz == null ? defaults(param) : defaults(param, claszz);
            //其他
            arithmetic(criteria, arithmetics);
            return criteria;
        }

        /**
         * 获取构造器
         */
        public Query query() {
            Query query = new Query().addCriteria(criteria());
            //排序
            if (orderBy != null && orderBy.size() > 0) {
                for (JLTuple.Tuple2<String, String> tuple2 : orderBy) {
                    query.with(Sort.by(tuple2.getV2().equals("asc") ? Sort.Order.asc(tuple2.getV1()) : Sort.Order.desc(tuple2.getV1())));
                }
            }
            return query;
        }

        private void set(JLLambda.JLFunction<T, ?> jlFunction, Object value, String way) {
            String property = JLLambda.getProperty(jlFunction);
            JLTuple.Tuple3<String, String, Object> tuple3 = new JLTuple.Tuple3<>(property, way, value);
            arithmetics.add(tuple3);
            notDefaultProperty.add(property);
        }

        /**
         * 参数校验
         */
        private boolean paramCheck(Object value) {
            if (value == null) {
                return true;
            }
            if (value instanceof String) {
                if ("".equals(value.toString().trim())) {
                    return true;
                }
            }
            return false;
        }

        /**
         * 其他构造条件处理
         */
        private void arithmetic(Criteria criteria, List<JLTuple.Tuple3<String, String, Object>> arithmetics) {
            for (JLTuple.Tuple3<String, String, Object> arithmeticObj : arithmetics) {
                String property = arithmeticObj.getV1();
                String arithmetic = arithmeticObj.getV2();
                Object value = arithmeticObj.getV3();
                if (paramCheck(value)) {
                    continue;
                }
                if (arithmetic.equals(EQ)) {
                    criteria.and(property).is(value);
                    continue;
                }
                if (arithmetic.equals(LT)) {
                    criteria.and(property).lt(value);
                    continue;
                }
                if (arithmetic.equals(GT)) {
                    criteria.and(property).gt(value);
                    continue;
                }
                if (arithmetic.equals(LE)) {
                    criteria.and(property).lte(value);
                    continue;
                }
                if (arithmetic.equals(GE)) {
                    criteria.and(property).gte(value);
                    continue;
                }
                if (arithmetic.equals(LIKE)) {
                    criteria.and(property).regex(".*?" + value + ".*", "i");
                    continue;
                }
                if (arithmetic.equals(NE)) {
                    criteria.and(property).ne(value);
                    continue;
                }
                if (arithmetic.equals(IN)) {
                    if (value instanceof List) {
                        List list = (List) value;
                        if (list.size() == 0) {
                            continue;
                        }
                        criteria.and(property).in(list);
                    }
                    continue;
                }
                if (arithmetic.equals(NOT_IN)) {
                    if (value instanceof List) {
                        List list = (List) value;
                        if (list.size() == 0) {
                            continue;
                        }
                        criteria.and(property).nin(list);
                    }
                    continue;
                }
            }
        }
    }
}